// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Threading;
using System.Runtime.Serialization;

namespace System.Data.SqlClient.ManualTesting.Tests
{
    /// <summary>
    /// Random number generator that tracks information necessary to reproduce a sequence of random numbers. To save the current state,
    /// tests should query the CurrentState property before execution of the test case that uses random.
    /// In addition, the test needs to capture the current state of random instance before setup steps, if random is used during setup.
    ///
    /// This class also provides more helper methods, like create numbers with high probability for low numbers.
    ///
    /// Use the CurrentState property to save repro information, and, when crash is detected, dump the state to file or log
    /// with its ToString method. The performance of CurrentState has been optimized to ensure it does not affect the overall performance
    /// of the test app. But, the CurrentState.ToString is not optimum, so use it only when crash is detected.
    ///
    /// For easy Random scope and repro management, see also RandomizerScope class.
    ///
    /// Note: instances of this class are not thread-safe!
    /// <example>
    /// To remember the current state of Randomizer, use randomizerInstance.CurrentState method before its first use!
    /// To recreate the randomizer object from its log, use Randomizer.Create(state) method.
    /// </example>
    /// </summary>
    public class Randomizer
    {
        /// <summary>
        /// counter used to prevent same seed generation for multi-threaded operations
        /// </summary>
        private static int s_seedCounter;

        /// <summary>
        /// Generates a seed value based on current ticks count (Environment.TickCount) XORed with
        /// incrementing counter to prevent duplicates if called within the interval of the 500 milliseconds
        /// (time resolution of OS ticks). This method is thread-safe!
        /// </summary>
        public static int CreateSeed()
        {
            return Environment.TickCount ^ Interlocked.Increment(ref s_seedCounter);
        }

        /// <summary>
        /// encapsulate the randomizer state which is a byte array within the read-only structure
        /// </summary>
        /// <remarks>Keep it immutable - randomizer pool creates shallow copies of the state struct only!</remarks>
        public struct State
        {
            internal readonly Type _randomizerType;
            internal readonly byte[] _binState;

            internal State(Type randType, byte[] binState)
            {
                _binState = binState;
                _randomizerType = randType;
            }

            private const char Separator = ':';

            /// <summary>
            /// Creates human-readable string representation of the state.
            /// Use it only when you need to dump the rand state to the file or log.
            /// </summary>
            public override string ToString()
            {
                if (_binState == null && _randomizerType == null)
                    return string.Empty;

                return string.Format("{0}{1}{2}", _randomizerType.FullName, Separator, Convert.ToBase64String(_binState));
            }

            /// <summary>
            /// recreates the state instance from string, generated by ToString()
            /// </summary>
            public static State Parse(string strState)
            {
                if (string.IsNullOrEmpty(strState))
                    return State.Empty;

                string[] items = strState.Split(new char[] { Separator }, 2);
                return new State(
                    Type.GetType(items[0]),
                    Convert.FromBase64String(items[1]));
            }

            /// <summary>
            /// represents an empty randomizer state, it cannot be used to create a randomizer!
            /// </summary>
            public static readonly State Empty = new State(null, null);
        }

        /// <summary>
        /// creates a new randomizer from seed
        /// Target Randomizer implementation must have an explicit c-tor that accepts seed only!
        /// </summary>
        public static RandomizerType Create<RandomizerType>(int seed)
            where RandomizerType : Randomizer, new()
        {
            if (typeof(RandomizerType) == typeof(Randomizer))
            {
                // optimization for Randomizer itself to avoid reflection call
                return (RandomizerType)new Randomizer(seed);
            }
            else
            {
                return (RandomizerType)Activator.CreateInstance(typeof(RandomizerType), new object[] { seed });
            }
        }

        /// <summary>
        /// Recreates randomizer from the state, state must not be empty!
        /// Target Randomizer implementation must have an explicit c-tor that accepts state only and calls into base c-tor(state)!
        /// </summary>
        public static RandomizerType Create<RandomizerType>(State state)
            where RandomizerType : Randomizer, new()
        {
            if (typeof(RandomizerType) == typeof(Randomizer))
            {
                // optimization for Randomizer itself to avoid redundant construction
                return (RandomizerType)new Randomizer(state);
            }
            else
            {
                return (RandomizerType)Activator.CreateInstance(typeof(RandomizerType), new object[] { state });
            }
        }

        /// <summary>
        /// Used to create an instance of randomizer from its state. Use Create static method to create the instance.
        /// </summary>
        /// <remarks>when overriding Randomizer, explicitly define a new protected constructor that accepts state only</remarks>
        protected Randomizer(State state)
        {
            Deserialize(state);
        }

        /// <summary>
        /// gets the current state of the Randomizer
        /// To trace it, you can use state.ToString() method.
        /// </summary>
        public State GetCurrentState()
        {
            byte[] binState = new byte[BinaryStateSize];
            int offset;
            Serialize(binState, out offset);
            return new State(GetType(), binState);
        }

        /// <summary>
        /// returns size needed to serialize the randomizer state, override this method to reserve space for custom fields.
        /// </summary>
        protected virtual int BinaryStateSize
        {
            get
            {
                return BinaryStateSizeRandom;
            }
        }

        /// <summary>
        /// this method is used to serialize the state, binState size must be BinaryStateSize
        /// </summary>
        /// <remarks>
        /// call base.Serialize first when overriding this method and use the nextOffset to write custom data
        /// </remarks>
        /// <remarks>
        /// For fast performance, avoid using MemoryStream/BinaryReader or reflection/serialization. I measured the difference between several implementations and found that:
        /// * Difference in time performance between using plain byte array versus MemoryStream with BinaryReader is ~ 1/10!
        /// * Difference between this implementation and serialization via .NET Serialization is ~1/100!
        /// </remarks>
        protected virtual void Serialize(byte[] binState, out int nextOffset)
        {
            nextOffset = 0;
            SerializeInt(_inext, binState, ref nextOffset);
            SerializeInt(_inextp, binState, ref nextOffset);

            Buffer.BlockCopy(_seedArray, 0, binState, nextOffset, 4 * _seedArray.Length);
            nextOffset += 4 * _seedArray.Length;
        }

        private void Deserialize(State state)
        {
            if (state._randomizerType != GetType())
                throw new ArgumentException("Type mismatch!");

            int offset;
            Deserialize(state._binState, out offset);
        }

        /// <summary>
        /// this method is used to deserialize the randomizer from its binary state, binState size is BinaryStateSize
        /// </summary>
        /// <remarks>
        /// call base.Deserialize first when overriding this method and use the nextOffset to read custom data
        /// </remarks>
        protected internal virtual void Deserialize(byte[] binState, out int nextOffset)
        {
            if (binState == null)
                throw new ArgumentNullException("empty state should not be used to create an instance of randomizer!");
            if (binState.Length != BinaryStateSize)
                throw new ArgumentNullException("wrong size of the state binary data");

            nextOffset = 0;
            _inext = DeserializeInt(binState, ref nextOffset);
            _inextp = DeserializeInt(binState, ref nextOffset);

            Buffer.BlockCopy(binState, nextOffset, _seedArray, 0, 4 * _seedArray.Length);
            nextOffset += 4 * _seedArray.Length;
        }

        /// <summary>
        /// helper method to serialize int field
        /// </summary>
        protected static void SerializeInt(int val, byte[] buf, ref int offset)
        {
            uint uval = unchecked((uint)val);
            buf[offset++] = (byte)((uval & 0x000000FF));
            buf[offset++] = (byte)((uval & 0x0000FF00) >> 8);
            buf[offset++] = (byte)((uval & 0x00FF0000) >> 16);
            buf[offset++] = (byte)((uval & 0xFF000000) >> 24);
        }

        /// <summary>
        /// helper method to deserialize int field
        /// </summary>
        protected static int DeserializeInt(byte[] buf, ref int offset)
        {
            uint uval = 0;
            uval |= (uint)(0X000000FF & buf[offset++]);
            uval |= (uint)(0X0000FF00 & (buf[offset++] << 8));
            uval |= (uint)(0X00FF0000 & (buf[offset++] << 16));
            uval |= (uint)(0XFF000000 & (buf[offset++] << 24));
            return unchecked((int)uval);
        }


        /// <summary>
        /// deserialization constructor
        /// </summary>
        public Randomizer(StreamingContext context)
        {
            string base64State = GetCurrentState().ToString();
            int offset;
            Deserialize(Convert.FromBase64String(base64State), out offset);
        }


        /// <summary>
        /// use this method to create seeds for nested randomizers out of current one
        /// </summary>
        public int NextSeed()
        {
            return Next();
        }

        #region DO NOT CHANGE BEYOND THIS POINT
        // The code below was copied from https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Random.cs
        // I copy-pasted the source instead of inheritance to get super-fast performance on state management (see State Management section above)
        // which is 100 times faster than using Reflection. See remarks on Serialize method for more details.

        //
        // Private Constants
        //
        private const int MBIG = int.MaxValue;
        private const int MSEED = 161803398;
        private const int MZ = 0;


        //
        // Member Variables - make sure to update state size and update serialization code when adding more members
        //
        private const int BinaryStateSizeRandom = 4 + 4 + 56 * 4;
        private int _inext;
        private int _inextp;
        private int[] _seedArray = new int[56];

        //
        // Public Constants
        //

        //
        // Native Declarations
        //

        //
        // Constructors
        //

        public Randomizer()
            : this(CreateSeed())
        {
        }

        public Randomizer(int Seed)
        {
            int ii;
            int mj, mk;

            //Initialize our Seed array.
            //This algorithm comes from Numerical Recipes in C (2nd Ed.)
            int subtraction = (Seed == int.MinValue) ? int.MaxValue : Math.Abs(Seed);
            mj = MSEED - subtraction;
            _seedArray[55] = mj;
            mk = 1;
            for (int i = 1; i < 55; i++)
            {  //Apparently the range [1..55] is special (Knuth) and so we're wasting the 0'th position.
                ii = (21 * i) % 55;
                _seedArray[ii] = mk;
                mk = mj - mk;
                if (mk < 0) mk += MBIG;
                mj = _seedArray[ii];
            }
            for (int k = 1; k < 5; k++)
            {
                for (int i = 1; i < 56; i++)
                {
                    _seedArray[i] -= _seedArray[1 + (i + 30) % 55];
                    if (_seedArray[i] < 0) _seedArray[i] += MBIG;
                }
            }
            _inext = 0;
            _inextp = 21;
            Seed = 1;
        }

        //
        // Package Private Methods
        //

        /*====================================Sample====================================
        **Action: Return a new random number [0..1) and reSeed the Seed array.
        **Returns: A double [0..1)
        **Arguments: None
        **Exceptions: None
        ==============================================================================*/
        protected virtual double Sample()
        {
            //Including this division at the end gives us significantly improved
            //random number distribution.
            return (InternalSample() * (1.0 / MBIG));
        }

        private int InternalSample()
        {
            int retVal;
            int locINext = _inext;
            int locINextp = _inextp;

            if (++locINext >= 56) locINext = 1;
            if (++locINextp >= 56) locINextp = 1;

            retVal = _seedArray[locINext] - _seedArray[locINextp];

            if (retVal == MBIG) retVal--;
            if (retVal < 0) retVal += MBIG;

            _seedArray[locINext] = retVal;

            _inext = locINext;
            _inextp = locINextp;

            return retVal;
        }

        //
        // Public Instance Methods
        //


        /*=====================================Next=====================================
        **Returns: An int [0..int.MaxValue)
        **Arguments: None
        **Exceptions: None.
        ==============================================================================*/
        public virtual int Next()
        {
            return InternalSample();
        }

        private double GetSampleForLargeRange()
        {
            // The distribution of double value returned by Sample
            // is not distributed well enough for a large range.
            // If we use Sample for a range [int.MinValue..int.MaxValue)
            // We will end up getting even numbers only.

            int result = InternalSample();
            // Note we can't use addition here. The distribution will be bad if we do that.
            bool negative = (InternalSample() % 2 == 0) ? true : false;  // decide the sign based on second sample
            if (negative)
            {
                result = -result;
            }
            double d = result;
            d += (int.MaxValue - 1); // get a number in range [0 .. 2 * Int32MaxValue - 1)
            d /= 2 * (uint)int.MaxValue - 1;
            return d;
        }


        /*=====================================Next=====================================
        **Returns: An int [minvalue..maxvalue)
        **Arguments: minValue -- the least legal value for the Random number.
        **           maxValue -- One greater than the greatest legal return value.
        **Exceptions: None.
        ==============================================================================*/
        public virtual int Next(int minValue, int maxValue)
        {
            if (minValue > maxValue)
            {
                throw new ArgumentOutOfRangeException("minValue > maxValue");
            }

            long range = (long)maxValue - minValue;
            if (range <= (long)int.MaxValue)
            {
                return ((int)(Sample() * range) + minValue);
            }
            else
            {
                return (int)((long)(GetSampleForLargeRange() * range) + minValue);
            }
        }


        /*=====================================Next=====================================
        **Returns: An int [0..maxValue)
        **Arguments: maxValue -- One more than the greatest legal return value.
        **Exceptions: None.
        ==============================================================================*/
        public virtual int Next(int maxValue)
        {
            if (maxValue < 0)
            {
                throw new ArgumentOutOfRangeException("maxValue < 0");
            }
            return (int)(Sample() * maxValue);
        }


        /*=====================================Next=====================================
        **Returns: A double [0..1)
        **Arguments: None
        **Exceptions: None
        ==============================================================================*/
        public virtual double NextDouble()
        {
            return Sample();
        }


        /*==================================NextBytes===================================
        **Action:  Fills the byte array with random bytes [0..0x7f].  The entire array is filled.
        **Returns:Void
        **Arguments:  buffer -- the array to be filled.
        **Exceptions: None
        ==============================================================================*/
        public virtual void NextBytes(byte[] buffer)
        {
            if (buffer == null) throw new ArgumentNullException(nameof(buffer));
            for (int i = 0; i < buffer.Length; i++)
            {
                buffer[i] = (byte)(InternalSample() % (byte.MaxValue + 1));
            }
        }

        #endregion
    }
}
